/**
 * @author nttdocomo
 */
define(function(require) {
	require("./aries");
	require("./lang/array");
	require("./abstractComponent");
	require("./util/floating");
	aries.klass("aries.Component", aries.AbstractComponent.extend({
		/**
		 * @cfg {String/String[]} ui
		 * A set style for a component. Can be a string or an Array of multiple strings (UIs)
		 */
		ui : 'default',

		/**
		 * @cfg {String[]} uiCls
		 * An array of of classNames which are currently applied to this component
		 * @private
		 */
		uiCls : [],
		baseCls : aries.baseCSSPrefix + 'component',

		/**
		 * Invoked after the Component is shown (after #onShow is called).
		 *
		 * Gets passed the same parameters as #show.
		 *
		 * @param {String/Ext.Element} [animateTarget]
		 * @param {Function} [callback]
		 * @param {Object} [scope]
		 *
		 * @template
		 * @protected
		 */
		afterShow : function(animateTarget, cb, scope) {
			var me = this, fromBox, toBox, ghostPanel;

			// Default to configured animate target if none passed
			animateTarget = animateTarget || me.animateTarget;

			// Need to be able to ghost the Component
			if (!me.ghost) {
				animateTarget = null;
			}
			// If we're animating, kick of an animation of the ghost from the target to the *Element* current box
			if (animateTarget) {
				animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
				toBox = me.el.getBox();
				fromBox = animateTarget.getBox();
				me.el.addCls(Ext.baseCSSPrefix + 'hide-offsets');
				ghostPanel = me.ghost();
				ghostPanel.el.stopAnimation();

				// Shunting it offscreen immediately, *before* the Animation class grabs it ensure no flicker.
				ghostPanel.el.setX(-10000);

				ghostPanel.el.animate({
					from : fromBox,
					to : toBox,
					listeners : {
						afteranimate : function() {
							delete ghostPanel.componentLayout.lastComponentSize;
							me.unghost();
							me.el.removeCls(Ext.baseCSSPrefix + 'hide-offsets');
							me.onShowComplete(cb, scope);
						}
					}
				});
			} else {
				me.onShowComplete(cb, scope);
			}
		},

		beforeRender : function() {
			var floating = this.floating, cls;

			if (floating) {
				this.addCls(aries.baseCSSPrefix + 'layer');

				cls = floating.cls;
				if (cls) {
					this.addCls(cls);
				}
			};
			aries.AbstractComponent.prototype.beforeRender.apply(this, arguments)
		},

		/**
		 * Invoked before the Component is shown.
		 *
		 * @method
		 * @template
		 * @protected
		 */
		beforeShow : aries.emptyFn,
		initialize : function(config) {
			config = config || {};
			aries.AbstractComponent.prototype.initialize.apply(this, arguments)
		},

		/**
		 * Method which adds a specified UI + uiCls to the components element. Can be overridden to remove the UI from more
		 * than just the components element.
		 * @param {String} ui The UI to remove from the element
		 */
		addUIClsToElement : function(cls) {
			var me = this, baseClsUi = me.baseCls + '-' + me.ui + '-' + cls, result = [aries.baseCSSPrefix + cls, me.baseCls + '-' + cls, baseClsUi], frameElementCls = me.frameElementCls, frameElementsArray, frameElementsLength, i, el, frameElement, c;

			if (me.frame && !$.support.borderRadius) {
				// define each element of the frame
				frameElementsArray = me.frameElementsArray;
				frameElementsLength = frameElementsArray.length;
				i = 0;

				// loop through each of them, and if they are defined add the ui
				for (; i < frameElementsLength; i++) {
					frameElement = frameElementsArray[i];
					el = me['frame' + frameElement.toUpperCase()];
					c = baseClsUi + '-' + frameElement;
					if (el && el.dom) {
						el.addCls(c);
					} else if (_.indexOf(frameElementCls[frameElement], c) == -1) {
						frameElementCls[frameElement].push(c);
					}
				}
			}

			me.frameElementCls = frameElementCls;

			return result;
		},

		/**
		 * Method which adds a specified UI to the components element.
		 * @private
		 */
		addUIToElement : function() {
			var me = this, baseClsUI = me.baseCls + '-' + me.ui, frameElementCls = me.frameElementCls, frameElementsArray, frameElementsLength, i, el, frameElement, c;

			me.addCls(baseClsUI);

			if (me.frame && !$.support.borderRadius) {
				// define each element of the frame
				frameElementsArray = me.frameElementsArray;
				frameElementsLength = frameElementsArray.length;
				i = 0;

				// loop through each of them, and if they are defined add the ui
				for (; i < frameElementsLength; i++) {
					frameElement = frameElementsArray[i];
					el = me['frame' + frameElement.toUpperCase()];
					c = baseClsUI + '-' + frameElement;
					if (el) {
						el.addCls(c);
					} else {
						if (!Ext.Array.contains(frameElementCls[frameElement], c)) {
							frameElementCls[frameElement].push(c);
						}
					}
				}
			}
		},

		/**
		 * Invoked after the Component has been hidden.
		 *
		 * Gets passed the same `callback` and `scope` parameters that #onHide received.
		 *
		 * @param {Function} [callback]
		 * @param {Object} [scope]
		 *
		 * @template
		 * @protected
		 */
		afterHide : function(cb, scope) {
			var me = this;
			delete me.hiddenByLayout;

			// we are the back-end method of onHide at this level, but our call to our parent
			// may need to be async... so callParent won't quite work here...
			//aries.AbstractComponent.prototype.onHide.call(this);

			//Ext.callback(cb, scope || me);
			me.trigger('hide', me);
		},

		// private
		// Implements an upward event bubbilng policy. By default a Component bubbles events up to its ownerCt
		// Floating Components target the floatParent.
		// Some Component subclasses (such as Menu) might implement a different ownership hierarchy.
		// The up() method uses this to find the immediate owner.
		getBubbleTarget : function() {
			return this.ownerCt || this.floatParent;
		},

		getId : function() {
			var me = this, xtype;

			if (!me.id) {
				xtype = me.getXType();
				if (xtype) {
					xtype = xtype.replace(aries.Component.INVALID_ID_CHARS_Re, '-');
				} else {
					xtype = aries.name.toLowerCase() + '-comp';
				}
				me.id = _.uniqueId(xtype + '-');
			}
			return me.id;
		},

		/**
		 * This function takes the position argument passed to onRender and returns a
		 * DOM element that you can use in the insertBefore.
		 * @param {String/Number/Ext.dom.Element/HTMLElement} position Index, element id or element you want
		 * to put this component before.
		 * @return {HTMLElement} DOM element that you can use in the insertBefore
		 */
		getInsertPosition : function(position) {
			// Convert the position to an element to insert before
			if (position !== undefined) {
				if (_.isNumber(position)) {
					position = this.container.childNodes[position];
				}
			}

			return position;
		},

		/**
		 * Gets the current XY position of the component's underlying element.
		 * @param {Boolean} [local=false] If true the element's left and top are returned instead of page XY.
		 * @return {Number[]} The XY position of the element (e.g., [100, 200])
		 */
		getPosition : function(local) {
			return this.$el.position();
			/*var me = this, el = me.el, xy, isContainedFloater = me.isContainedFloater(), floatParentBox;

			 // Floating Components which were just rendered with no ownerCt return local position.
			 if ((local === true) || !isContainedFloater) {
			 return [el.getLeft(true), el.getTop(true)];
			 }

			 // Use our previously set x and y properties if possible.
			 if (me.x !== undefined && me.y !== undefined) {
			 xy = [me.x, me.y];
			 } else {
			 xy = me.el.getXY();

			 // Floating Components in an ownerCt have to have their positions made relative
			 if (isContainedFloater) {
			 floatParentBox = me.floatParent.getTargetEl().getViewRegion();
			 xy[0] -= floatParentBox.left;
			 xy[1] -= floatParentBox.top;
			 }
			 }
			 return xy;*/
		},

		/**
		 * Gets the xtype for this component as registered with {@link Ext.ComponentManager}. For a list of all available
		 * xtypes, see the {@link Ext.Component} header. Example usage:
		 *
		 *     var t = new Ext.form.field.Text();
		 *     alert(t.getXType());  // alerts 'textfield'
		 *
		 * @return {String} The xtype
		 */
		getXType : function() {
			return this.constructor.xtype;
		},

		/**
		 * Checks if there is currently a specified uiCls
		 * @param {String} cls The cls to check
		 */
		hasUICls : function(cls) {
			var me = this, uiCls = me.uiCls || [];

			return $.inArray(cls, uiCls) === -1 ? false : true;
		},

		/**
		 * Hides this Component, setting it to invisible using the configured {@link #hideMode}.
		 * @param {String/Ext.Element/Ext.Component} [animateTarget=null] **only valid for {@link #floating} Components
		 * such as {@link Ext.window.Window Window}s or {@link Ext.tip.ToolTip ToolTip}s, or regular Components which have
		 * been configured with `floating: true`.**. The target to which the Component should animate while hiding.
		 * @param {Function} [callback] A callback function to call after the Component is hidden.
		 * @param {Object} [scope] The scope (`this` reference) in which the callback is executed.
		 * Defaults to this Component.
		 * @return {Ext.Component} this
		 */
		hide : function() {
			var me = this;

			// Clear the flag which is set if a floatParent was hidden while this is visible.
			// If a hide operation was subsequently called, that pending show must be hidden.
			me.showOnParentShow = false;

			if (!(me.rendered && !me.isVisible()) && me.trigger('beforehide', me) !== false) {
				me.hidden = true;
				if (me.rendered) {
					me.onHide.apply(me, arguments);
				}
			}
			return me;
		},

		/**
		 * Converts style definitions to String.
		 * @return {String} A CSS style string with style, padding, margin and border.
		 * @private
		 */
		initStyles : function(targetEl) {
			var me = this, padding = me.padding, margin = me.margin, x = me.x, y = me.y, width, height;

			// Convert the padding, margin and border properties from a space seperated string
			// into a proper style string
			if (padding !== undefined) {
				targetEl.css('padding', (padding === true) ? 5 : padding);
			}

			if (margin !== undefined) {
				targetEl.css('margin', (margin === true) ? 5 : margin);
			}

			if (me.border !== undefined) {
				me.setBorder(me.border, targetEl);
			}

			// initComponent, beforeRender, or event handlers may have set the style or cls property since the protoEl was set up
			// so we must apply styles and classes here too.
			if (me.cls && me.cls != me.initialCls) {
				targetEl.addCls(me.cls);
				delete me.cls;
				delete me.initialCls;
			}
			if (me.style && me.style != me.initialStyle) {
				targetEl.css(me.style);
				delete me.style;
				delete me.initialStyle;
			}

			if (x !== undefined) {
				targetEl.setStyle('left', x + 'px');
			}
			if (y !== undefined) {
				targetEl.setStyle('top', y + 'px');
			}

			// Framed components need their width/height to apply to the frame, which is
			// best handled in layout at present.
			// If we're using the content box model, we also cannot assign initial sizes since we do not know the border widths to subtract
			if (!me.getFrameInfo()/* && Ext.isBorderBox*/) {
				width = me.width;
				height = me.height;

				// framed components need their width/height to apply to the frame, which is
				// best handled in layout at present
				if (width !== undefined) {
					targetEl.css('width', ( typeof width === 'number') ? width + 'px' : width);
				}
				if (height !== undefined) {
					targetEl.css('height', ( typeof height === 'number') ? height + 'px' : height);
				}
			}
		},

		/**
		 * Initializes the renderTpl.
		 * @return {Ext.XTemplate} The renderTpl XTemplate instance.
		 * @private
		 */
		/*initRenderTpl : function() {
		 return this.renderTpl;
		 },*/
		initComponent : function() {
			this.initRenderTpl()
		},

		initContainer : function(container) {

			// If you render a component specifying the el, we get the container
			// of the el, and make sure we dont move the el around in the dom
			// during the render
			if (!container && this.el) {
				container = this.el.parentNode;
				this.allowDomMove = false;
			}
			this.container = container;

			return this.container;
		}/*,
		render : function(container,position) {
		var el = this.el, // ensure me.el is wrapped
		tree, nextSibling;

		container = this.initContainer(container);

		nextSibling = this.getInsertPosition(position);
		if(!el) {
		tree = this.getRenderTree();

		// tree will be null if a beforerender listener returns false
		if(tree) {
		if(nextSibling) {
		el = Ext.DomHelper.insertBefore(nextSibling, tree);
		} else {
		el = Ext.DomHelper.append(container, tree);
		}

		me.wrapPrimaryEl(el);
		}
		}

		this.$el.html((new this.renderTpl(this.initRenderData())).html());
		(container || $(document.body)).append(this.$el);
		this.$el.addClass(this.classList.join(" "))
		this.applyRenderSelectors();
		}*/,

		/**
		 * Possibly animates down to a target element.
		 *
		 * Allows addition of behavior to the hide operation. After
		 * calling the superclass’s onHide, the Component will be hidden.
		 *
		 * Gets passed the same parameters as #hide.
		 *
		 * @param {String/Ext.Element/Ext.Component} [animateTarget]
		 * @param {Function} [callback]
		 * @param {Object} [scope]
		 *
		 * @template
		 * @protected
		 */
		onHide : function(animateTarget, cb, scope) {
			var me = this, ghostPanel, toBox;

			// Default to configured animate target if none passed
			animateTarget = animateTarget || me.animateTarget;

			// Need to be able to ghost the Component
			if (!me.ghost) {
				animateTarget = null;
			}
			// If we're animating, kick off an animation of the ghost down to the target
			if (animateTarget) {
				animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
				ghostPanel = me.ghost();
				ghostPanel.el.stopAnimation();
				toBox = animateTarget.getBox();
				toBox.width += 'px';
				toBox.height += 'px';
				ghostPanel.el.animate({
					to : toBox,
					listeners : {
						afteranimate : function() {
							delete ghostPanel.componentLayout.lastComponentSize;
							ghostPanel.el.hide();
							me.afterHide(cb, scope);
						}
					}
				});
			}
			me.$el.hide();
			if (!animateTarget) {
				me.afterHide(cb, scope);
			}
		},

		/**
		 * Allows addition of behavior to the show operation. After
		 * calling the superclass's onShow, the Component will be visible.
		 *
		 * Override in subclasses where more complex behaviour is needed.
		 *
		 * Gets passed the same parameters as #show.
		 *
		 * @param {String/Ext.Element} [animateTarget]
		 * @param {Function} [callback]
		 * @param {Object} [scope]
		 *
		 * @template
		 * @protected
		 */
		onShow : function() {
			var me = this;

			me.$el.show();
			aries.AbstractComponent.prototype.onShow.apply(this, arguments);
			if (me.floating && me.constrain) {
				me.doConstrain();
			}
		},

		/**
		 * Removes a CSS class from the top level element representing this component.
		 * @param {String/String[]} cls The CSS class name to remove
		 * @returns {Ext.Component} Returns the Component to allow method chaining.
		 */
		removeCls : function(cls) {
			var me = this, el = this.$el;

			el.removeClass(_.isString(cls) ? cls : cls.join(" "));
			//el.removeClass(Array.prototype.slice.call(arguments, 0).join(" "));
			return me;
		},

		/**
		 * Method which removes a specified UI + uiCls from the components element. The cls which is added to the element
		 * will be: `this.baseCls + '-' + ui`
		 * @param {String} ui The UI to add to the element
		 */
		removeUIClsFromElement : function(cls) {
			var me = this, baseClsUi = me.baseCls + '-' + me.ui + '-' + cls, result = [aries.baseCSSPrefix + cls, me.baseCls + '-' + cls, baseClsUi], frameElementCls = me.frameElementCls, frameElementsArray, frameElementsLength, i, el, frameElement, c;

			if (me.frame && !$.support.borderRadius) {
				// define each element of the frame
				frameElementsArray = me.frameElementsArray;
				frameElementsLength = frameElementsArray.length;
				i = 0;

				// loop through each of them, and if they are defined add the ui
				for (; i < frameElementsLength; i++) {
					frameElement = frameElementsArray[i];
					el = me['frame' + frameElement.toUpperCase()];
					c = baseClsUi + '-' + frameElement;
					if (el && el.dom) {
						el.addCls(c);
					} else {
						aries.Array.remove(frameElementCls[frameElement], c);
					}
				}
			}

			me.frameElementCls = frameElementCls;

			return result;
		},

		/**
		 * Method which removes a specified UI from the components element.
		 * @private
		 */
		removeUIFromElement : function() {
			var me = this, baseClsUI = me.baseCls + '-' + me.ui, frameElementCls = me.frameElementCls, frameElementsArray, frameElementsLength, i, el, frameElement, c;

			me.removeCls(baseClsUI);

			if (me.frame && !Ext.supports.CSS3BorderRadius) {
				// define each element of the frame
				frameElementsArray = me.frameElementsArray;
				frameElementsLength = frameElementsArray.length;
				i = 0;

				for (; i < frameElementsLength; i++) {
					frameElement = frameElementsArray[i];
					el = me['frame' + frameElement.toUpperCase()];
					c = baseClsUI + '-' + frameElement;
					if (el) {
						el.removeCls(c);
					} else {
						aries.Array.remove(frameElementCls[frameElement], c);
					}
				}
			}
		},

		/**
		 *
		 * @param {String/Number} border The border, see {@link #border}. If a falsey value is passed
		 * the border will be removed.
		 */
		setBorder : function(border, /* private */targetEl) {
			var me = this, initial = !!targetEl;

			if (me.rendered || initial) {
				if (!initial) {
					targetEl = me.el;
				}

				if (!border) {
					border = 0;
				} else {
					border = (border === true) ? 1 : border;
				}
				targetEl.css('border-width', border);
				if (!initial) {
					me.updateLayout();
				}
			}
			me.border = border;
		},

		/**
		 * Sets the page XY position of the component. To set the left and top instead, use {@link #setPosition}.
		 * This method fires the {@link #move} event.
		 * @param {Number} x The new x position
		 * @param {Number} y The new y position
		 * @param {Boolean/Object} [animate] True to animate the Component into its new position. You may also pass an
		 * animation configuration.
		 * @return {Ext.Component} this
		 */
		setPagePosition : function(x, y, animate) {
			var me = this, p, floatParentBox;

			if (Ext.isArray(x)) {
				y = x[1];
				x = x[0];
			}
			me.pageX = x;
			me.pageY = y;

			if (me.floating) {

				// Floating Components which are registered with a Container have to have their x and y properties made relative
				if (me.isContainedFloater()) {
					floatParentBox = me.floatParent.getTargetEl().getViewRegion();
					if (Ext.isNumber(x) && Ext.isNumber(floatParentBox.left)) {
						x -= floatParentBox.left;
					}
					if (Ext.isNumber(y) && Ext.isNumber(floatParentBox.top)) {
						y -= floatParentBox.top;
					}
				} else {
					p = me.el.translatePoints(x, y);
					x = p.left;
					y = p.top;
				}

				me.setPosition(x, y, animate);
			} else {
				p = me.el.translatePoints(x, y);
				me.setPosition(p.left, p.top, animate);
			}

			return me;
		},

		/**
		 * Shows this Component, rendering it first if {@link #autoRender} or {@link #floating} are `true`.
		 *
		 * After being shown, a {@link #floating} Component (such as a {@link Ext.window.Window}), is activated it and
		 * brought to the front of its {@link #zIndexManager z-index stack}.
		 *
		 * @param {String/Ext.Element} [animateTarget=null] **only valid for {@link #floating} Components such as {@link
		 * Ext.window.Window Window}s or {@link Ext.tip.ToolTip ToolTip}s, or regular Components which have been configured
		 * with `floating: true`.** The target from which the Component should animate from while opening.
		 * @param {Function} [callback] A callback function to call after the Component is displayed.
		 * Only necessary if animation was specified.
		 * @param {Object} [scope] The scope (`this` reference) in which the callback is executed.
		 * Defaults to this Component.
		 * @return {Ext.Component} this
		 */
		show : function(animateTarget, cb, scope) {
			var me = this, rendered = me.rendered;

			if (rendered && me.isVisible()) {
				if (me.toFrontOnShow && me.floating) {
					me.toFront();
				}
			} else if (me.trigger('beforeshow', me) !== false) {
				// Render on first show if there is an autoRender config, or if this is a floater (Window, Menu, BoundList etc).
				me.hidden = false;
				if (!rendered && (me.autoRender || me.floating)) {
					me.doAutoRender();
					rendered = me.rendered;
				}

				if (rendered) {
					me.beforeShow();
					me.onShow.apply(me, arguments);
					//me.afterShow.apply(me, arguments);
				}
			}
			return me;
		}
	}, {
		DIRECTION_TOP : 'top',
		INVALID_ID_CHARS_Re : /[\.,\s]/g,
		initializer : function(fn) {
			return fn._owner = this, this._initializer = fn, this
		},
		setBaseView : function(view, options) {
			this._baseView = view;
			this._baseViewOptions = options;
			return this
		}
	})).mixin({
		floating : aries.util.Floating
	})
})